package jgroups.demos;

import java.awt.Color;
import java.awt.Container;
import java.awt.Dimension;
import java.awt.FlowLayout;
import java.awt.Font;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.FocusEvent;
import java.awt.event.FocusListener;
import java.lang.management.ManagementFactory;
import java.util.Map;
import java.util.concurrent.ConcurrentMap;

import javax.management.MBeanServer;
import javax.swing.BoxLayout;
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.JScrollPane;
import javax.swing.JTabbedPane;
import javax.swing.JTable;
import javax.swing.JTextArea;
import javax.swing.JTextField;
import javax.swing.JViewport;
import javax.swing.table.AbstractTableModel;
import javax.swing.table.TableModel;

import org.jgroups.Address;
import org.jgroups.MembershipListener;
import org.jgroups.View;
import org.jgroups.blocks.Cache;
import org.jgroups.blocks.ReplCache;
import org.jgroups.jmx.JmxConfigurator;

/**
 * GUI demo of ReplCache
 * @author Bela Ban
 */
public class ReplCacheDemo extends JPanel implements ActionListener {
	private ReplCache<String, String> cache;
	private static final String BASENAME = "replcache";

	private JFrame frame;
	private JTabbedPane root_pane = new JTabbedPane();
	private JTable table;
	private JTextField key_field = createTextField(null, 10);
	private JTextField value_field = createTextField(null, 10);
	private JTextField repl_count_field = createTextField("1", 3);
	private JTextField timeout_field = createTextField("0", 5);
	private JTextField perf_key_prefix = createTextField("key", 5);
	private JTextField perf_num_keys = createTextField("1000", 5);
	private JTextField perf_size = createTextField("1000", 5);
	private JTextField perf_repl_count_field = createTextField("1", 3);
	private JTextField perf_timeout_field = createTextField("0", 5);
	private JTextArea status = new JTextArea("Status area", 10, 5);
	private JLabel num_elements = new JLabel("0 elements");
	private MyTableModel model = null;

	public void actionPerformed(ActionEvent event) {
		String command = event.getActionCommand();

		if (command.equals("Put")) {
			String key = key_field.getText();
			String value = value_field.getText();
			String repl_count = repl_count_field.getText();
			String timeout = timeout_field.getText();

			if (key == null || value == null)
				return;

			if (repl_count == null)
				repl_count = "1";
			if (timeout == null)
				timeout = "0";

			cache.put(key, value, Short.valueOf(repl_count),
					Long.valueOf(timeout));
		} else if (command.equals("Remove")) {
			int[] rows = table.getSelectedRows();
			if (rows != null) {
				for (int row : rows) {
					String key = (String) model.getValueAt(row, 0);
					if (key != null)
						cache.remove(key);
				}
			}
		} else if (command.equals("Clear")) {
			clear();
		} else if (command.equals("Rebalance")) {
			cache.mcastEntries();
		} else if (command.equals("Reset")) {
			status.setText("");
		} else if (command.equals("Start")) {
			startPerfTest();
		} else if (command.equals("Stop")) {

		} else if (command.equals("Exit")) {
			if (cache != null)
				cache.stop();
			frame.dispose();
			System.exit(1); // or can we break out of mainLoop() somehow else ?
		}
	}

	private void clear() {
		cache.clear();
	}

	private void startPerfTest() {
		int num_puts = 1000;
		int size = 1000;
		short repl_count = 1;
		long timeout = 0;
		String key_prefix = "key";

		String tmp = perf_key_prefix.getText();
		if (tmp != null)
			key_prefix = tmp;
		tmp = perf_num_keys.getText();
		if (tmp != null)
			num_puts = Integer.valueOf(tmp);
		tmp = perf_size.getText();
		if (tmp != null)
			size = Integer.valueOf(tmp);
		tmp = perf_repl_count_field.getText();
		if (tmp != null)
			repl_count = Short.valueOf(tmp);
		tmp = perf_timeout_field.getText();
		if (tmp != null)
			timeout = Long.valueOf(tmp);

		long start = System.currentTimeMillis();
		for (int i = 0; i < num_puts; i++) {
			String key = key_prefix + "-" + i;
			String value = "val-" + i;
			cache.put(key, value, repl_count, timeout);
		}
		long diff = System.currentTimeMillis() - start;
		status.setText("It took " + diff + " ms to insert " + num_puts
				+ " elements");
	}

	private void start(String props, String cluster_name, long rpc_timeout,
			long caching_time, boolean migrate_data, boolean use_l1_cache,
			int l1_max_entries, long l1_reaping_interval, int l2_max_entries,
			long l2_reaping_interval) throws Exception {
		MBeanServer server = ManagementFactory.getPlatformMBeanServer();

		cache = new ReplCache<String, String>(props, cluster_name);
		cache.setCallTimeout(rpc_timeout);
		cache.setCachingTime(caching_time);
		cache.setMigrateData(migrate_data);
		JmxConfigurator.register(cache, server, BASENAME + ":name=cache");
		JmxConfigurator.register(cache.getL2Cache(), server, BASENAME
				+ ":name=l2-cache");

		if (use_l1_cache) {
			Cache<String, String> l1_cache = new Cache<String, String>();
			cache.setL1Cache(l1_cache);
			if (l1_reaping_interval > 0)
				l1_cache.enableReaping(l1_reaping_interval);
			if (l1_max_entries > 0)
				l1_cache.setMaxNumberOfEntries(l1_max_entries);
			JmxConfigurator.register(cache.getL1Cache(), server, BASENAME
					+ ":name=l1-cache");
		}

		if (l2_max_entries > 0 || l2_reaping_interval > 0) {
			Cache<String, ReplCache.Value<String>> l2_cache = cache
					.getL2Cache();
			if (l2_max_entries > 0)
				l2_cache.setMaxNumberOfEntries(l2_max_entries);
			if (l2_reaping_interval > 0)
				l2_cache.enableReaping(l2_reaping_interval);
		}

		Runtime.getRuntime().addShutdownHook(new Thread() {
			public void run() {
				cache.stop();
			}
		});

		cache.start();

		model = new MyTableModel<String, String>();
		model.setMap(cache.getL2Cache().getInternalMap());
		cache.addChangeListener(model);

		frame = new JFrame("ReplCacheDemo");
		frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);

		setLayout(new BoxLayout(this, BoxLayout.Y_AXIS));

		table = new MyTable(model);
		table.setPreferredScrollableViewportSize(new Dimension(500, 200));
		// table.setFillsViewportHeight(true); // JDK 6 specific
		table.setShowGrid(false);
		table.setFont(table.getFont().deriveFont(Font.BOLD));
		add(new JScrollPane(table));

		JPanel key = new JPanel(new FlowLayout(FlowLayout.LEFT));
		key.add(new JLabel("Key  "));
		key.add(key_field);
		add(key);

		JPanel value = new JPanel(new FlowLayout(FlowLayout.LEFT));
		value.add(new JLabel("Value"));
		value.add(value_field);
		add(value);

		JPanel repl_count = new JPanel(new FlowLayout(FlowLayout.LEFT));
		repl_count.add(new JLabel("Replication count"));
		repl_count.add(repl_count_field);
		add(repl_count);

		JPanel timeout = new JPanel(new FlowLayout(FlowLayout.LEFT));
		timeout.add(new JLabel("Timeout"));
		timeout.add(timeout_field);
		add(timeout);

		JPanel buttons = new JPanel();
		JButton put_button = createButton("Put");
		buttons.add(createButton("Put"));
		buttons.add(createButton("Remove"));
		buttons.add(createButton("Clear"));
		buttons.add(createButton("Rebalance"));
		buttons.add(createButton("Exit"));
		buttons.add(num_elements);
		add(buttons);
		setOpaque(true);

		root_pane.addTab("Data", this);
		JPanel perf_panel = new JPanel();
		perf_panel.setLayout(new BoxLayout(perf_panel, BoxLayout.Y_AXIS));
		perf_panel.setOpaque(true);
		root_pane.addTab("Perf test", perf_panel);

		perf_panel.add(status);
		status.setForeground(Color.BLUE);

		JPanel prefix = new JPanel(new FlowLayout(FlowLayout.LEFT));
		prefix.add(new JLabel("Key prefix"));
		prefix.add(perf_key_prefix);
		perf_panel.add(prefix);

		JPanel keys = new JPanel(new FlowLayout(FlowLayout.LEFT));
		keys.add(new JLabel("Number of keys to insert"));
		keys.add(perf_num_keys);
		perf_panel.add(keys);

		JPanel size = new JPanel(new FlowLayout(FlowLayout.LEFT));
		size.add(new JLabel("Size of each key (bytes)"));
		size.add(perf_size);
		size.add(new JLabel("    (ignored for now)"));
		perf_panel.add(size);

		JPanel perf_repl_count = new JPanel(new FlowLayout(FlowLayout.LEFT));
		perf_repl_count.add(new JLabel("Replication count"));
		perf_repl_count.add(perf_repl_count_field);
		perf_panel.add(perf_repl_count);

		JPanel perf_timeout = new JPanel(new FlowLayout(FlowLayout.LEFT));
		perf_timeout.add(new JLabel("Timeout"));
		perf_timeout.add(perf_timeout_field);
		perf_panel.add(perf_timeout);

		JPanel perf_buttons = new JPanel(new FlowLayout(FlowLayout.LEFT));
		perf_buttons.add(createButton("Start"));
		perf_buttons.add(createButton("Stop"));
		perf_buttons.add(createButton("Reset"));
		perf_buttons.add(createButton("Exit"));
		perf_panel.add(perf_buttons);

		frame.setContentPane(root_pane);
		frame.pack();
		frame.getRootPane().setDefaultButton(put_button);
		frame.setVisible(true);
		setTitle("ReplCacheDemo");

		cache.addMembershipListener(new MembershipListener() {
			public void viewAccepted(View new_view) {
				setTitle("ReplCacheDemo");
			}

			public void suspect(Address suspected_mbr) {
			}

			public void block() {
			}

			public void unblock() {
			}
		});
	}

	private JButton createButton(String text) {
		JButton retval = new JButton(text);
		retval.addActionListener(this);
		return retval;
	}

	private static JTextField createTextField(String name, int length) {
		JTextField retval = new JTextField(name, length);
		retval.addFocusListener(new MyFocusListener(retval));
		return retval;
	}

	public static void main(String[] args) throws Exception {
		String props = "udp.xml";
		String cluster_name = "replcache-cluster";
		long rpc_timeout = 1500L, caching_time = 30000L;
		boolean migrate_data = true, use_l1_cache = true;
		int l1_max_entries = 5000, l2_max_entries = -1;
		long l1_reaping_interval = -1, l2_reaping_interval = 30000L;

		for (int i = 0; i < args.length; i++) {
			if (args[i].equals("-props")) {
				props = args[++i];
				continue;
			}
			if (args[i].equals("-cluster_name")) {
				cluster_name = args[++i];
				continue;
			}
			if (args[i].equals("-rpc_timeout")) {
				rpc_timeout = Long.parseLong(args[++i]);
				continue;
			}
			if (args[i].equals("-caching_time")) {
				caching_time = Long.parseLong(args[++i]);
				continue;
			}
			if (args[i].equals("-migrate_data")) {
				migrate_data = Boolean.parseBoolean(args[++i]);
				continue;
			}
			if (args[i].equals("-use_l1_cache")) {
				use_l1_cache = Boolean.parseBoolean(args[++i]);
				continue;
			}
			if (args[i].equals("-l1_max_entries")) {
				l1_max_entries = Integer.parseInt(args[++i]);
				continue;
			}
			if (args[i].equals("-l1_reaping_interval")) {
				l1_reaping_interval = Long.parseLong(args[++i]);
				continue;
			}
			if (args[i].equals("-l2_max_entries")) {
				l2_max_entries = Integer.parseInt(args[++i]);
				continue;
			}
			if (args[i].equals("-l2_reaping_interval")) {
				l2_reaping_interval = Long.parseLong(args[++i]);
				continue;
			}

			help();
			return;
		}

		ReplCacheDemo demo = new ReplCacheDemo();
		demo.start(props, cluster_name, rpc_timeout, caching_time,
				migrate_data, use_l1_cache, l1_max_entries,
				l1_reaping_interval, l2_max_entries, l2_reaping_interval);
	}

	void setTitle(String title) {
		String local_addr = cache != null ? cache.getLocalAddressAsString()
				: null;
		int num_nodes = cache != null ? cache.getClusterSize() : 0;
		frame.setTitle(title + ": " + local_addr + " (" + num_nodes + ")");
	}

	private static void help() {
		System.out
				.println("ReplCacheServer [-help] [-props <props>] [-cluster_name <name>]"
						+ "[-rpc_timeout <ms>] [-caching_time <ms>] "
						+ "[-migrate_data <true|false>] [-use_l1_cache <true|false>] "
						+ "[-l1_max_entries <num>] [-l1_reaping_interval <ms>] "
						+ "[-l2_max_entries <num>] [-l2_reaping_interval <ms>] ");
	}

	private static class MyFocusListener implements FocusListener {
		private final JTextField field;

		public MyFocusListener(JTextField field) {
			this.field = field;
		}

		public void focusGained(FocusEvent e) {
			String value = field.getText();
			if (value != null && value.length() > 0) {
				field.selectAll();
			}
		}

		public void focusLost(FocusEvent e) {
		}
	}

	private static class MyTable extends JTable {

		private MyTable(TableModel dm) {
			super(dm);
		}

		public boolean getScrollableTracksViewportHeight() {
			Container viewport = getParent();
			return viewport instanceof JViewport
					&& getPreferredSize().height < viewport.getHeight();
		}

	}

	private class MyTableModel<K, V> extends AbstractTableModel implements
			ReplCache.ChangeListener {
		private ConcurrentMap<K, Cache.Value<ReplCache.Value<V>>> map;
		private final String[] columnNames = { "Key", "Value",
				"Replication Count", "Timeout" };
		private static final long serialVersionUID = 1314724464389654329L;

		public void setMap(ConcurrentMap<K, Cache.Value<ReplCache.Value<V>>> map) {
			this.map = map;
		}

		public int getColumnCount() {
			return columnNames.length;
		}

		public int getRowCount() {
			return map.size();
		}

		public String getColumnName(int col) {
			return columnNames[col];
		}

		public Object getValueAt(int row, int col) {
			int count = 0;

			for (Map.Entry<K, Cache.Value<ReplCache.Value<V>>> entry : map
					.entrySet()) {
				if (count++ >= row) {
					K key = entry.getKey();
					Cache.Value<ReplCache.Value<V>> val = entry.getValue();
					ReplCache.Value<V> tmp = val.getValue();
					switch (col) {
					case 0:
						return key;
					case 1:
						V value = tmp.getVal();
						return value instanceof byte[] ? ((byte[]) value).length
								+ " bytes"
								: value;
					case 2:
						return tmp.getReplicationCount();
					case 3:
						return val.getTimeout();
					default:
						return "n/a";
					}
				}
			}
			throw new IllegalArgumentException("row=" + row + ", col=" + col);

		}

		public void changed() {
			fireTableDataChanged();
			num_elements.setText(cache.getL2Cache().getSize() + " elements");
		}
	}

}
